﻿namespace AvaloniaWebView.Helpers;

internal class BorderRenderHelper
{
    private bool _useComplexRendering;
    private bool? _backendSupportsIndividualCorners;
    private StreamGeometry? _backgroundGeometryCache;
    private StreamGeometry? _borderGeometryCache;
    private Size _size;
    private Thickness _borderThickness;
    private CornerRadius _cornerRadius;
    private bool _initialized;


    void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius)
    {
        _backendSupportsIndividualCorners = true;
        //_backendSupportsIndividualCorners ??= AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>().SupportsIndividualRoundRects;
        _size = finalSize;
        _borderThickness = borderThickness;
        _cornerRadius = cornerRadius;
        _initialized = true;

        if (borderThickness.IsUniform && (cornerRadius.IsUniform || _backendSupportsIndividualCorners == true))
        {
            _backgroundGeometryCache = null;
            _borderGeometryCache = null;
            _useComplexRendering = false;
        }
        else
        {
            _useComplexRendering = true;

            var boundRect = new Rect(finalSize);
            var innerRect = boundRect.Deflate(borderThickness);
            BorderGeometryKeypoints? backgroundKeypoints = null;
            StreamGeometry? backgroundGeometry = null;

            if (innerRect.Width != 0 && innerRect.Height != 0)
            {
                backgroundGeometry = new StreamGeometry();
                backgroundKeypoints = new BorderGeometryKeypoints(innerRect, borderThickness, cornerRadius, true);

                using var ctx = backgroundGeometry.Open();
                CreateGeometry(ctx, innerRect, backgroundKeypoints);

                _backgroundGeometryCache = backgroundGeometry;
            }
            else
                _backgroundGeometryCache = null;

            if (boundRect.Width != 0 && boundRect.Height != 0)
            {
                var borderGeometryKeypoints =
                    new BorderGeometryKeypoints(boundRect, borderThickness, cornerRadius, false);
                var borderGeometry = new StreamGeometry();

                using var ctx = borderGeometry.Open();
                CreateGeometry(ctx, boundRect, borderGeometryKeypoints);

                if (backgroundGeometry != null)
                    CreateGeometry(ctx, innerRect, backgroundKeypoints!);

                _borderGeometryCache = borderGeometry;
            }
            else
                _borderGeometryCache = null;
        }
    }

    public void Render(DrawingContext context,
                       Size finalSize, Thickness borderThickness, CornerRadius cornerRadius,
                       IBrush? background, IBrush? borderBrush, BoxShadows boxShadows, double borderDashOffset = 0,
                       PenLineCap borderLineCap = PenLineCap.Flat, PenLineJoin borderLineJoin = PenLineJoin.Miter,
                       AvaloniaList<double>? borderDashArray = null)
    {
        if (_size != finalSize || _borderThickness != borderThickness || _cornerRadius != cornerRadius || !_initialized)
            Update(finalSize, borderThickness, cornerRadius);

        RenderCore(context, background, borderBrush, boxShadows, borderDashOffset, borderLineCap, borderLineJoin, borderDashArray);
    }

    void RenderCore(DrawingContext context, IBrush? background, IBrush? borderBrush, BoxShadows boxShadows,
        double borderDashOffset, PenLineCap borderLineCap, PenLineJoin borderLineJoin,
        AvaloniaList<double>? borderDashArray)
    {
        if (_useComplexRendering)
        {
            var backgroundGeometry = _backgroundGeometryCache;
            if (backgroundGeometry != null)
                context.DrawGeometry(background, null, backgroundGeometry);

            var borderGeometry = _borderGeometryCache;
            if (borderGeometry != null)
                context.DrawGeometry(borderBrush, null, borderGeometry);
        }
        else
        {
            var borderThickness = _borderThickness.Top;
            IPen? pen = null;
            ImmutableDashStyle? dashStyle = null;

            if (borderDashArray != null && borderDashArray.Count > 0)
                dashStyle = new ImmutableDashStyle(borderDashArray, borderDashOffset);

            if (borderBrush != null && borderThickness > 0)
                pen = new ImmutablePen(borderBrush.ToImmutable(), borderThickness, dashStyle, borderLineCap, borderLineJoin);

            var rect = new Rect(_size);
            if (!MathUtilities.IsZero(borderThickness))
                rect = rect.Deflate(borderThickness * 0.5);
            var rrect = new RoundedRect(rect, _cornerRadius.TopLeft, _cornerRadius.TopRight, _cornerRadius.BottomRight, _cornerRadius.BottomLeft);

            context.DrawRectangle(background, pen, rrect, boxShadows);
        }
    }

    private static void CreateGeometry(StreamGeometryContext context, Rect boundRect,
        BorderGeometryKeypoints keypoints)
    {
        context.BeginFigure(keypoints.TopLeft, true);

        context.LineTo(keypoints.TopRight);

        var radiusX = boundRect.TopRight.X - keypoints.TopRight.X;
        var radiusY = keypoints.RightTop.Y - boundRect.TopRight.Y;
        if (radiusX != 0 || radiusY != 0)
            context.ArcTo(keypoints.RightTop, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);

        context.LineTo(keypoints.RightBottom);

        radiusX = boundRect.BottomRight.X - keypoints.BottomRight.X;
        radiusY = boundRect.BottomRight.Y - keypoints.RightBottom.Y;
        if (radiusX != 0 || radiusY != 0)
            context.ArcTo(keypoints.BottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);

        context.LineTo(keypoints.BottomLeft);

        radiusX = keypoints.BottomLeft.X - boundRect.BottomLeft.X;
        radiusY = boundRect.BottomLeft.Y - keypoints.LeftBottom.Y;
        if (radiusX != 0 || radiusY != 0)
            context.ArcTo(keypoints.LeftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);

        // Left
        context.LineTo(keypoints.LeftTop);

        // TopLeft corner
        radiusX = keypoints.TopLeft.X - boundRect.TopLeft.X;
        radiusY = keypoints.LeftTop.Y - boundRect.TopLeft.Y;

        if (radiusX != 0 || radiusY != 0)
            context.ArcTo(keypoints.TopLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);

        context.EndFigure(true);
    }

    private class BorderGeometryKeypoints
    {
        internal BorderGeometryKeypoints(Rect boundRect, Thickness borderThickness, CornerRadius cornerRadius, bool inner)
        {
            var left = 0.5 * borderThickness.Left;
            var top = 0.5 * borderThickness.Top;
            var right = 0.5 * borderThickness.Right;
            var bottom = 0.5 * borderThickness.Bottom;

            double leftTopY;
            double topLeftX;
            double topRightX;
            double rightTopY;
            double rightBottomY;
            double bottomRightX;
            double bottomLeftX;
            double leftBottomY;

            if (inner)
            {
                leftTopY = Math.Max(0, cornerRadius.TopLeft - top) + boundRect.TopLeft.Y;
                topLeftX = Math.Max(0, cornerRadius.TopLeft - left) + boundRect.TopLeft.X;
                topRightX = boundRect.Width - Math.Max(0, cornerRadius.TopRight - top) + boundRect.TopLeft.X;
                rightTopY = Math.Max(0, cornerRadius.TopRight - right) + boundRect.TopLeft.Y;
                rightBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomRight - bottom) +
                               boundRect.TopLeft.Y;
                bottomRightX = boundRect.Width - Math.Max(0, cornerRadius.BottomRight - right) +
                               boundRect.TopLeft.X;
                bottomLeftX = Math.Max(0, cornerRadius.BottomLeft - left) + boundRect.TopLeft.X;
                leftBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomLeft - bottom) +
                              boundRect.TopLeft.Y;
            }
            else
            {
                leftTopY = cornerRadius.TopLeft + top + boundRect.TopLeft.Y;
                topLeftX = cornerRadius.TopLeft + left + boundRect.TopLeft.X;
                topRightX = boundRect.Width - (cornerRadius.TopRight + right) + boundRect.TopLeft.X;
                rightTopY = cornerRadius.TopRight + top + boundRect.TopLeft.Y;
                rightBottomY = boundRect.Height - (cornerRadius.BottomRight + bottom) + boundRect.TopLeft.Y;
                bottomRightX = boundRect.Width - (cornerRadius.BottomRight + right) + boundRect.TopLeft.X;
                bottomLeftX = cornerRadius.BottomLeft + left + boundRect.TopLeft.X;
                leftBottomY = boundRect.Height - (cornerRadius.BottomLeft + bottom) + boundRect.TopLeft.Y;
            }

            var leftTopX = boundRect.TopLeft.X;
            var topLeftY = boundRect.TopLeft.Y;
            var topRightY = boundRect.TopLeft.Y;
            var rightTopX = boundRect.Width + boundRect.TopLeft.X;
            var rightBottomX = boundRect.Width + boundRect.TopLeft.X;
            var bottomRightY = boundRect.Height + boundRect.TopLeft.Y;
            var bottomLeftY = boundRect.Height + boundRect.TopLeft.Y;
            var leftBottomX = boundRect.TopLeft.X;

            LeftTop = new Point(leftTopX, leftTopY);
            TopLeft = new Point(topLeftX, topLeftY);
            TopRight = new Point(topRightX, topRightY);
            RightTop = new Point(rightTopX, rightTopY);
            RightBottom = new Point(rightBottomX, rightBottomY);
            BottomRight = new Point(bottomRightX, bottomRightY);
            BottomLeft = new Point(bottomLeftX, bottomLeftY);
            LeftBottom = new Point(leftBottomX, leftBottomY);

            // Fix overlap
            if (TopLeft.X > TopRight.X)
            {
                var scaledX = topLeftX / (topLeftX + topRightX) * boundRect.Width;
                TopLeft = new Point(scaledX, TopLeft.Y);
                TopRight = new Point(scaledX, TopRight.Y);
            }

            if (RightTop.Y > RightBottom.Y)
            {
                var scaledY = rightBottomY / (rightTopY + rightBottomY) * boundRect.Height;
                RightTop = new Point(RightTop.X, scaledY);
                RightBottom = new Point(RightBottom.X, scaledY);
            }

            if (BottomRight.X < BottomLeft.X)
            {
                var scaledX = bottomLeftX / (bottomLeftX + bottomRightX) * boundRect.Width;
                BottomRight = new Point(scaledX, BottomRight.Y);
                BottomLeft = new Point(scaledX, BottomLeft.Y);
            }

            if (LeftBottom.Y < LeftTop.Y)
            {
                var scaledY = leftTopY / (leftTopY + leftBottomY) * boundRect.Height;
                LeftBottom = new Point(LeftBottom.X, scaledY);
                LeftTop = new Point(LeftTop.X, scaledY);
            }
        }

        internal Point LeftTop { get; }

        internal Point TopLeft { get; }

        internal Point TopRight { get; }

        internal Point RightTop { get; }

        internal Point RightBottom { get; }

        internal Point BottomRight { get; }

        internal Point BottomLeft { get; }

        internal Point LeftBottom { get; }
    }
}